<!doctype html>
<html>
<head>
  <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chunked Upload (native JS)</title>

  <script src='/assets/qrcode.js' type="text/javascript"></script>
  <script src='/assets/toast.js' type="text/javascript"></script>
  <link rel="stylesheet" href='/assets/toast.css'></style>
  <link rel="stylesheet" href='/assets/style.css'></style>
  <style>
    body{font-family: Arial; padding:20px}
    .log{white-space:pre-wrap; background:#f7f7f7; padding:10px; border-radius:6px;}
    progress{width:100%; height:20px}
  </style>
  <style>
    :root {
      --primary-color: #4285f4;
      --hover-color: #f1f3f4;
      --border-color: #e0e0e0;
      --text-color: #202124;
      --secondary-text: #5f6368;
    }
    .flex{display:flex;}
    .file-list-container {
      border-radius: 8px;
      overflow: hidden;
      background: white;
    }
    
    .folder-item, .file-item {
      display: flex;
      align-items: center;
      padding: 10px 16px;
      transition: all 0.2s ease;
      border-bottom: 1px solid var(--border-color);
      cursor: pointer;
    }
    
    .folder-item {
      background-color: #f8f9fa;
      font-weight: 500;
    }
    
    .folder-item:hover, .file-item:hover {
      background-color: var(--hover-color);
    }
    
    .folder-icon {
      color: var(--primary-color);
      margin-right: 12px;
      font-size: 1.2em;
    }
    
    .file-icon {
      margin-right: 12px;
      color: var(--secondary-text);
      font-size: 1.1em;
    }
    
    .tree-indent {
      display: flex;
      width: 24px;
      justify-content: center;
      position: relative;
    }
    
    .tree-line {
      position: absolute;
      left: 50%;
      top: -10px;
      bottom: -10px;
      width: 1px;
      background: var(--border-color);
      z-index: 1;
    }
    
    .tree-line::after {
      content: '';
      position: absolute;
      left: 0;
      top: 50%;
      width: 12px;
      height: 1px;
      background: var(--border-color);
    }
    
    .file-info {
      display: flex;
      align-items: center;
      flex-grow: 1;
      overflow: hidden;
    }
    
    .file-name {
      word-break: break-word;
      color: var(--text-color);
      line-height: 1.4;
      max-width: 50%;
    }
    
    .file-meta {
      display: flex;
      gap: 16px;
      color: var(--secondary-text);
      font-size: 0.85em;
      margin-left: auto;
      padding-left: 16px;
    }
    
    .download-btn {
      background: var(--primary-color);
      color: white;
      border: none;
      padding: 6px 12px;
      border-radius: 4px;
      cursor: pointer;
      transition: all 0.2s;
      font-size: 0.85em;
      margin-left: 12px;
      width:75px;
    }
    
    .download-btn:hover {
      background: #3367d6;
      box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    }
    
    @media (max-width: 600px) {
      .folder-item, .file-item {
        padding: 12px;
        flex-direction: column;
        align-items: flex-start;
      }
      .file-info {
        width: 100%;
        flex-direction: column;
        align-items: flex-start;
      }
      .file-name {
        max-width: 100%;
        margin-bottom: 4px;
      }
      .file-meta {
        width: 100%;
        flex-direction: column;
        gap: 4px;
        font-size: 0.85em;
        margin-left: 0;
        padding-left: 0;
        margin-top: 8px;
      }
      .download-btn {
        margin-top: 8px;
        margin-left: 0;
        width: 70px;
        padding: 8px;
      }
    }
    /* 嵌套文件缩进样式 */
    .nested-file {
      margin-left: 24px;
    }
    
    .nested-file-level-2 {
      margin-left: 48px;
    }
    
    .nested-file-level-3 {
      margin-left: 72px;
    }
  </style>
</head>
<body>
  <div class="flex">
    <div id="msg"></div>
  <button id="copyBtn">复制</button>
  </div>
  <input type="text" id="jsonmsg" placeholder="输入要设置的消息">
  <button id="modifymsg">修改</button>
  <div id="qrcode" style="display: flex;flex-wrap: wrap;place-content: center;"></div>
  <h2>分片上传（原生 JS）</h2>
    
  <input id="file" type="file" multiple><br><br>
  Chunk size (bytes): <input id="chunkSize" type="number" value="5242880"> (5MB) <br><br>
  <button id="startUpload">开始上传</button>
  <p><span id="log2"></span>: <progress id="progress" value="0" max="100"></progress></p>
  <div class="log" id="log"></div>
    <div class="log" id="log2"></div>


    <div style="margin-top: 30px; border-top: 2px solid #ccc; padding-top: 20px;">
    <h2>下载文件</h2>
    <button onclick="loadFileList(new URLSearchParams(window.location.search).get('dir') || 'uploads')">刷新文件列表</button>
    <div id="fileList" style="margin-top: 10px;">
      <p>点击"刷新文件列表"按钮查看可下载的文件</p>
    </div>
  </div>
  <script>
    var qrcode = new QRCode(document.getElementById("qrcode"), {
	text: window.location.href,
	width: 128,
	height: 128,
	colorDark : "#000000",
	colorLight : "#ffffff",
	correctLevel : QRCode.CorrectLevel.H
});

// 从 db.json 读取 msg 值并显示在页面上
fetch('/db.json')
  .then(response => response.json())
  .then(data => {
    document.getElementById('msg').textContent = data.msg;
    document.getElementById('jsonmsg').value = data.msg; // 同时设置输入框的值
  })
  .catch(error => {
    console.error('Error fetching db.json:', error);
  });

// 修改 db.json 中 msg 值的功能
const modifyBtn = document.getElementById('modifymsg');
if (modifyBtn) {
  modifyBtn.addEventListener('click', async () => {
    const newMsg = document.getElementById('jsonmsg').value;
    
    try {
      const response = await fetch('/update-db', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ msg: newMsg })
      });
      
      const result = await response.json();
      
      if (result.success) {
        // 更新显示的消息
        document.getElementById('msg').textContent = newMsg;
        console.log('db.json 更新成功');
        
        // 显示成功提示
        showToast({
          msg: '消息更新成功',
          type: 'success',
          time: 2
        });
      } else {
        console.error('更新失败:', result.error);
        
        // 显示错误提示
        showToast({
          msg: '消息更新失败: ' + (result.error || '未知错误'),
          type: 'error',
          time: 3
        });
      }
    } catch (error) {
      console.error('请求失败:', error);
      
      // 显示错误提示
      showToast({
        msg: '请求失败: ' + error.message,
        type: 'error',
        time: 3
      });
    }
  });
}
      // 页面加载完成后自动加载文件列表
  window.addEventListener('DOMContentLoaded', () => {
    // 从URL获取目录参数
    const urlParams = new URLSearchParams(window.location.search);
    const dir = urlParams.get('dir') || 'uploads';
    loadFileList(dir);
  });
  const logEl = document.getElementById('log');
  const logEl2 = document.getElementById('log2');
  function log(msg,dom= document.getElementById('log')){
    dom.textContent = msg 
  }
  

  
async function multipleUpload(idx){
    const fileInput = document.getElementById('file');
    console.log('======fileInput.value',fileInput.value);
    const file = fileInput.files[idx];
    if (!file){ alert('请选择要上传的文件'); return; }
    const chunkSize = Number(document.getElementById('chunkSize').value) || 5*1024*1024;
    const totalChunks = Math.ceil(file.size / chunkSize);
    const filename = encodeURIComponent(file.name);

    log(`Start upload: ${file.name}, size=${file.size}, chunks=${totalChunks}`);

    const progress = document.getElementById('progress');

    for (let i = 0; i < totalChunks; i++){
      const start = i * chunkSize;
      const end = Math.min(file.size, start + chunkSize);
      const chunk = file.slice(start, end);

      // send chunk as raw body; metadata in query params
      const url = `/upload?filename=${filename}&chunkIndex=${i}&totalChunks=${totalChunks}`;

      try {
        const resp = await fetch(url, {
          method: 'POST',
          body: chunk
        });
        if (resp.ok){
         console.log('======完成了一个切片');
        }else{
           const txt = await resp.text();
          throw new Error(`upload chunk ${i} failed: ${txt}`);
        }
      } catch (e){
        log('Error uploading chunk ' + i + ': ' + e.message);
        alert('Upload failed: ' + e.message);
        return;
      }

      const percent = Math.round(((i+1)/totalChunks)*100);
      progress.value = percent;
      log(`Uploaded chunk ${i+1}/${totalChunks} (${percent}%)`);
    }

    log('上传完成 — 当所有chunk上传完成后，服务器会自动合并文件.');
    //alert('浏览器端上传完成，请等待服务器合并文件');

}
  document.getElementById('startUpload').onclick = async () => {
    const fileInput = document.getElementById('file');
    if (!fileInput.value){ alert('请选择要上传的文件'); return; }
    for(var i=0;i<document.getElementById('file').files.length;i++){ 
      log(`当前第${i+1}个文件进度`,logEl2)
await multipleUpload(i)
    } 
          log(`所有文件已完成`,logEl2)
          document.getElementById('file').value = '';
  };

   // 加载服务器上的文件列表
  async function loadFileList(dir = 'uploads') {
    const fileListElement = document.getElementById('fileList');
    fileListElement.innerHTML = '<p>正在加载文件列表...</p>';
    
    try {
      const response = await fetch(`/list-files?dir=${encodeURIComponent(dir)}`);
      if (!response.ok) {
        throw new Error('获取文件列表失败');
      }
      
      const files = await response.json();
      
      // 显示当前目录
      const currentDirElement = document.createElement('div');
      currentDirElement.style.marginBottom = '10px';
      currentDirElement.innerHTML = `123123
        <strong>当前目录: ${dir}</strong>
        <div style="margin-top: 5px;">
          <button onclick="loadFileList('uploads')" ${dir === 'uploads' ? 'disabled' : ''}>uploads</button>
          <button onclick="loadFileList('downloads')" ${dir === 'downloads' ? 'disabled' : ''}>downloads</button>
        </div>
      `;
     
      fileListElement.appendChild(currentDirElement);
      
      if (files.length === 0) {
        fileListElement.innerHTML = '<p>没有可下载的文件</p>';
        return;
      }
      // 创建文件列表表格
      // 移动端优先的响应式设计
      let tableHTML = `<div class="file-list-container">`;
      
      // 显示所有文件和文件夹
      files.forEach(item => {
        console.log('======item.isDirectory',item);
        if (item.isDirectory) {
          tableHTML += `
            <div class="folder-item" >
              <div class="folder-info">
                <div class="folder-name">📁 ${item.name}</div>
              </div>
            </div>
          `;
        } else {
          // 格式化文件大小
          const fileSize = formatFileSize(item.size);
          // 格式化日期
          const lastModified = new Date(item.lastModified).toLocaleString();
          // 获取文件图标
          const fileIcon = getFileIcon(item.name);
          
          // 计算嵌套层级
          const pathDepth = item.name.split('/').length - 1;
          const indentClass = pathDepth === 1 ? 'nested-file' : 
                            pathDepth === 2 ? 'nested-file-level-2' :
                            pathDepth >= 3 ? 'nested-file-level-3' : '';
          
          tableHTML += `
            <div class="file-item ${indentClass}">
              <div class="file-info">
                ${item.name.includes('/') ? 
                  `<div class="file-path-indicator">
                    ${item.name.split('/').slice(0, -1).map(() => '<span class="path-segment"></span>').join('')}
                  </div>` : ''
                }
                <div class="file-name">
                  ${getFileIcon(item.name)} ${item.name.split('/').pop()}
                </div>
                <div class="file-meta">
                  <span>大小: ${formatFileSize(item.size)}</span>
                  <span>上传: ${new Date(item.lastModified).toLocaleString()}</span>
                  <span style="font-size:0.8em;color:#888;">路径: ${item.name.includes('/') ? item.name.split('/').slice(0,-1).join('/') : '根目录'}</span>
                </div>
              </div>
              <div>
                <button class="download-btn" onclick="downloadFile('${encodeURIComponent(item.name)}')">
                  下载
                </button>
              </div>
            </div>
          `;
        }
      });
        tableHTML += `</div>`;
      
      
      
      tableHTML += `</div>`;
      
      fileListElement.innerHTML = tableHTML;
      //fileListElement.insertAdjacentHTML('afterbegin', currentDirElement.innerHTML);
      
    } catch (error) {
      console.error('加载文件列表失败:', error);
      const currentDir = new URLSearchParams(window.location.search).get('dir') || 'uploads';
      fileListElement.innerHTML = `
        <div style="color: red; margin-bottom: 10px;">
          <p>加载文件列表失败: ${error.message}</p>
          <button onclick="loadFileList('${currentDir}')" style="margin-top: 10px;">
            重试加载
          </button>
        </div>
      `;
      showToast({
        msg: `加载 ${currentDir} 目录文件列表失败: ${error.message}`,
        type: 'error',
        time: 5
      });
    }
  }

    // 下载文件
  function downloadFile(filename) {
    // 从当前URL获取目录参数
    const currentUrl = new URL(window.location.href);
    const dir = currentUrl.searchParams.get('dir') || 'uploads';
    
    // 验证目录名称
    if (!/^[a-zA-Z0-9_-]+$/.test(dir)) {
      showToast({
        msg: '无效的目录名称',
        type: 'error',
        time: 3
      });
      return;
    }

    const downloadUrl = `/download/${filename}?dir=${encodeURIComponent(dir)}`;
    
    // 创建一个隐藏的a标签来触发下载
    const link = document.createElement('a');
    link.href = downloadUrl;
    link.download = decodeURIComponent(filename); // 设置下载的文件名
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    showToast({
      msg: `开始下载: ${decodeURIComponent(filename)} (来自 ${dir} 目录)`,
      type: 'info',
      time: 2
    });
  }
  
  // 获取文件图标
  function getFileIcon(filename) {
    const ext = filename.split('.').pop().toLowerCase();
    const icons = {
      pdf: '📄',
      doc: '📄', docx: '📄',  // 文档类统一使用📄
      xls: '📄', xlsx: '📄',  // 表格类统一使用📄
      ppt: '📄', pptx: '📄',  // 演示类统一使用📄
      jpg: '🖼️', jpeg: '🖼️', png: '🖼️', gif: '🖼️',  // 图片类使用🖼️
      mp3: '🎵', wav: '🎵',  // 音频类使用🎵
      mp4: '🎬', avi: '🎬', mov: '🎬',  // 视频类使用🎬
      zip: '🗃️', rar: '🗃️', '7z': '🗃️',  // 压缩包使用🗃️
      exe: '⚙️', dmg: '⚙️',  // 可执行文件使用⚙️
      txt: '📄', csv: '📄',  // 文本类使用📄
      html: '🌐', htm: '🌐',  // 网页类使用🌐
      js: '📄', json: '📄'  // 代码类使用📄
    };
    return icons[ext] || '🌈';  // 默认使用文件夹图标�
  }

  // 格式化文件大小
  function formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }

const copyBtn = document.getElementById('copyBtn');
const msg = document.getElementById('msg');

   // 复制文本功能
            copyBtn.addEventListener('click', function() {
                // 选择文本
                const range = document.createRange();
                range.selectNodeContents(msg);
                const selection = window.getSelection();
                selection.removeAllRanges();
                selection.addRange(range);
                
                try {
                    // 执行复制命令
                    const successful = document.execCommand('copy');
                    if (successful) {
                    showToast({
                      msg: '文本已成功复制到剪贴板！',
                      type: 'success',
                      time: 2
                    });
                       
                    } else {
                      showToast({
                        msg: '复制失败，请手动选择文本后复制',
                        type: 'success',
                        time: 2
                      });
                       
                    }
                } catch (err) {
                    console.error('复制失败:', err);
                  showToast({
                    msg: '复制失败，您的浏览器可能不支持此功能',
                    type: 'success',
                    time: 2
                  });
                }
                
                // 清除选择
                selection.removeAllRanges();
            });
  </script>
</body>
</html>